/*
 * Copyright (c) 1990, 2021, shwfed and/or its affiliates. All rights reserved.
 *
 *
 */
package cn.junglead.common.utils.cipher;

import cn.junglead.common.utils.exception.ActiveException;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * RSA 双向非对称加密算法工具.
 * 
 * @author Gary.Wang
 * @version 1.0
 */
@Slf4j
public final class CipherRsaUtil extends BaseCipherUtil {

	private CipherRsaUtil(){}

	/**
	 * 公钥
	 */
	public static final String PUBLIC_KEY = "RSAPublicKey";
	/**
	 * 私钥
	 */
	public static final String PRIVATE_KEY = "RSAPrivateKey";
	/**
	 * 默认的算法
	 */
	private static final String DEFAULT_ALGORITHMS = "RSA/ECB/PKCS1Padding";

	private static final Integer DEFAULT_KEY_SIZE = 2048;

	/**
	 * 签名算法还可以取值： MD5WithRSA
	 */
	private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

	private static final Map<Integer, Integer> INPUT_SIZE = new HashMap<>();

	static {
		INPUT_SIZE.put(2048, 245);
		INPUT_SIZE.put(4096, 501);
	}

	/**
	 * 根据默认算法和长度生成 RSA 双向非对称加密的密钥对，默认算法为 RSA/ECB/PKCS1Padding , 长度为 2048
	 * @return 返回生成的 RSA 密钥对，其中 RSAPublicKey 为公钥键名， RSAPrivateKey 为私钥键名。可能返回空对象。
	 */
    public static Map<String, Object> generateKeygenPair() {
		return generateKeygenPair(DEFAULT_KEY_SIZE);
    }

	/**
	 * 指定算法以及秘钥长度，生成 RSA 双向非对称加密的密钥对
	 * @param keySize 密钥长度，只能为 2048 或者 4096
	 * @return 返回生成的 RSA 密钥对，其中 RSAPublicKey 为公钥键名， RSAPrivateKey 为私钥键名。可能返回空对象。
	 */
	public static Map<String, Object> generateKeygenPair(int keySize){
		Map<String, Object> keyMap = new HashMap<>(2);
		try{
			KeyPair keyPair = generateKeyPair(keySize);
			RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
			RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
			keyMap.put(PUBLIC_KEY, publicKey);
			keyMap.put(PRIVATE_KEY, privateKey);
			return keyMap;
		} catch (Exception exception){
			log.error("生成 RSA 秘钥对时发生异常，异常信息：{}", exception.getMessage(), exception);
		}
		return keyMap;
	}

	/**
	 * 使用默认算法和秘钥长度进行 RSA 加密
	 *
	 * @param plaintext 待加密的明文文本
	 * @param publicKeyValue 公钥文本
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, String publicKeyValue) {
		return encrypt(plaintext, publicKeyValue, DEFAULT_KEY_SIZE);
	}

	/**
	 * 使用默认算法并指定秘钥长度进行 RSA 加密
	 * @param plaintext 待加密的明文文本
	 * @param publicKeyValue 公钥文本
	 * @param keySize 密钥长度
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, String publicKeyValue, int keySize) {
		return encrypt(plaintext, publicKeyValue, DEFAULT_ALGORITHMS, keySize);
	}

	/**
	 * 使用指定算法和秘钥长度，根据公钥文本内容进行 RSA 加密
	 * @param plaintext 待加密的明文文本
	 * @param publicKeyValue 公钥文本
	 * @param algorithms 加密算法
	 * @param keySize 密钥长度
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, String publicKeyValue, String algorithms, Integer keySize) {
		try {
			return encrypt(plaintext, CipherRsaUtil.convertPublicKey(publicKeyValue), algorithms, keySize);
		} catch (InvalidKeySpecException | 	NoSuchAlgorithmException exception) {
			log.error("RSA 公钥文本转公钥对象发生异常，异常信息：{}", exception.getMessage(), exception);
		}
		return null;
	}

	/**
	 * 使用指定算法和秘钥长度进行 RSA 解密
	 * @param ciphertext 待解密的密文文本
	 * @param privateKeyValue 私钥文本
	 * @return 返回解密后的结果
	 */
	public static String decrypt(String ciphertext, String privateKeyValue){
		return decrypt(ciphertext, privateKeyValue, DEFAULT_ALGORITHMS);
	}

	/**
	 * 使用指定算法和秘钥长度进行 RSA 解密
	 * @param ciphertext 待解密的密文文本
	 * @param privateKeyValue 私钥文本
	 * @param algorithms 解密算法
	 * @return 返回解密后的结果
	 */
	public static String decrypt(String ciphertext, String privateKeyValue, String algorithms){
		try {
			return decrypt(ciphertext, CipherRsaUtil.convertPrivateKey(privateKeyValue), algorithms);
		} catch (InvalidKeySpecException | 	NoSuchAlgorithmException exception) {
			log.error("RSA 私钥文本转私钥对象发生异常，异常信息：{}", exception.getMessage(), exception);
		}
		return null;
	}

	/**
	 * 指定算法和秘钥长度进行 RSA 加密的核心方法
	 * @param plaintext 待加密的明文文本
	 * @param publicKey 公钥对象
	 * @param algorithms 加密算法
	 * @param keySize 密钥长度
	 * @return 返回加密后的结果
	 */
	public static String encrypt(String plaintext, RSAPublicKey publicKey, String algorithms, Integer keySize) {
		try {
			byte[] input = plaintext.getBytes(StandardCharsets.UTF_8);
			verifyKeyAndContentLength(keySize, input);

			Cipher cipher = createCipher(algorithms, Cipher.ENCRYPT_MODE, publicKey);
			return Base64.getEncoder().encodeToString(cipher.doFinal(input));
		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException |
				 InvalidAlgorithmParameterException e) {
			log.error("RSA 公钥对象加密发生错误：{}", e.getMessage());
		}
		return null;
    }

    /**
     * 指定算法和秘钥长度进行 RSA 解密的核心方法
     *
     * @param ciphertext 待解密的密文文本
     * @param privateKey 私钥对象
     * @return 返回解密后的结果
     */
    public static String decrypt(String ciphertext, RSAPrivateKey privateKey, String algorithms) {
    	try {
    		byte[] input = Base64.getDecoder().decode(ciphertext);

			Cipher cipher = createCipher(algorithms, Cipher.DECRYPT_MODE, privateKey);
			return new String(cipher.doFinal(input), StandardCharsets.UTF_8);
    		
    	} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException e) {
			log.error("RSA 私钥对象解密发生错误：{}", e.getMessage());
		}
    	return null;
    }
    
    /**
     * 获取公钥文本内容
     *
     * @param keyMap 密钥对
     * @return 返回文本内容
     */
    public static String getPublicKeyValue(Map<String, Object> keyMap) {
    	Key key = (Key) keyMap.get(PUBLIC_KEY);
    	return Base64.getEncoder().encodeToString(key.getEncoded());
    }
    
    /**
     * 
     * 获取私钥文本内容
     *
     * @param keyMap 密钥对
     * @return 返回文本内容
     */
    public static String getPrivateKeyValue(Map<String, Object> keyMap) {
    	Key key = (Key) keyMap.get(PRIVATE_KEY);
    	return Base64.getEncoder().encodeToString(key.getEncoded());
    }

	/**
	 * 获取签名
	 *
	 * @param content 待签名的内容
	 * @param privateKeyValue 私钥文本
	 * @return 返回签名
	 */
	public static String sign(String content, String privateKeyValue) {
		try {
			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
			signature.initSign(CipherRsaUtil.convertPrivateKey(privateKeyValue));
			signature.update(content.getBytes(StandardCharsets.UTF_8));
			return Base64.getEncoder().encodeToString(signature.sign());
		} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | SignatureException e) {
			log.error("RSA 签名发生错误：{}", e.getMessage());
		}
		return null;
	}

	/**
	 * 验证签名
	 *
	 * @param content 待验证的内容
	 * @param sign 签名内容
	 * @param publicKeyValue 公钥文本
	 * @return 返回验证结果
	 */
	public static boolean checkSign(String content, String sign, String publicKeyValue) {
		try {
			Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
			signature.initVerify(CipherRsaUtil.convertPublicKey(publicKeyValue));
			signature.update(content.getBytes(StandardCharsets.UTF_8));
			return signature.verify(Base64.getDecoder().decode(sign));
		} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | SignatureException e) {
			log.error("RSA 验证签名发生错误：{}", e.getMessage());
		}
		return false;
	}

	/**
	 * 根据指定算法及长度，生成非对称算法（如 RSA）的随机秘钥
	 * @param keySize 密钥长度，最小为 1024
	 * @return 返回密钥对对象
	 * @throws NoSuchAlgorithmException 不存在的算法异常
	 */
	private static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
		keyPairGenerator.initialize(keySize);
		return keyPairGenerator.generateKeyPair();
	}

	/**
	 * 验证长度
	 * @param input 输入对象
	 * @param size 返回结果
	 */
	private static void verifyKeyAndContentLength(int size, byte[] input){
		if (!INPUT_SIZE.containsKey(size)){
			throw new ActiveException(CipherRsaUtil.class, "RSA 秘钥只支持 2048 和 4096 长度");
		}

		if (input.length > INPUT_SIZE.get(size)){
			throw new ActiveException(CipherRsaUtil.class, "RSA 加密字符串长度不能大于 117 位");
		}
	}

	/**
	 * 公钥字符串转换为公钥对象
	 *
	 * @param publicKeyValue 公钥文本
	 * @return 返回转换后的公钥对象
	 * @throws InvalidKeySpecException 错误的Key指定异常
	 * @throws NoSuchAlgorithmException 错误的算法异常
	 */
	private static RSAPublicKey convertPublicKey(String publicKeyValue) throws InvalidKeySpecException, NoSuchAlgorithmException {
		byte[] input = Base64.getDecoder().decode(publicKeyValue);
		return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(input));
	}

	/**
	 * 私钥字符串转换为私钥对象
	 *
	 * @param privateKeyValue 私钥文本
	 * @return 返回转换后的私钥对象
	 * @throws InvalidKeySpecException 错误的Key指定异常
	 * @throws NoSuchAlgorithmException 错误的算法异常
	 */
	private static RSAPrivateKey convertPrivateKey(String privateKeyValue) throws InvalidKeySpecException, NoSuchAlgorithmException {
		byte[] input = Base64.getDecoder().decode(privateKeyValue);
		return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(input));
	}
}
